package com.bnz.search.service.impl;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.SortOptions;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregation;
import co.elastic.clients.elasticsearch._types.aggregations.StringTermsAggregate;
import co.elastic.clients.elasticsearch._types.aggregations.StringTermsBucket;
import co.elastic.clients.elasticsearch._types.query_dsl.MultiMatchQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import co.elastic.clients.elasticsearch._types.query_dsl.RangeQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.TermQuery;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Highlight;
import co.elastic.clients.elasticsearch.core.search.HighlightField;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.elasticsearch.core.search.HitsMetadata;
import co.elastic.clients.json.JsonData;
import com.alibaba.fastjson.JSON;
import com.bnz.search.entity.ItemEntity;
import com.bnz.search.entity.ItemVo;
import com.bnz.search.service.ItemSearchService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.query.SortQuery;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

@Service
public class ItemSearchServiceImpl5 implements ItemSearchService {
    @Autowired
    private ElasticsearchClient client;
    @Autowired
    private StringRedisTemplate redisTemplate;
    /**
     * 1. 进行各种查询
     *
     * @param vo
     * @return
     * @throws IOException
     */
    @Override
    public Map search(ItemVo vo) throws IOException {
        //new map
        Map resultMap = new HashMap();
        //根据参数进行查询--关键词
        if (vo != null) {
            //定义商评列表集合
            List<ItemEntity> itemList = new ArrayList<>();
            //定义分类列表集合
            List<String> categoryList = new ArrayList<>();
            // 定义所有的查询集合
            List<Query> listQuery = new ArrayList<>();
            //定义分类查询
            Aggregation categoryAgg = Aggregation.of(a->a.terms(t->t.field("category")));

            //定义分类查询         -转换 _toQuery 为了lanmda表达式来识别
            //判断识别
            if(StringUtils.isNotBlank(vo.getCategory())) {
                Query categoryQuery = TermQuery.of(category -> category.value(vo.getCategory()).field("category"))._toQuery();
                listQuery.add(categoryQuery);
            }
            //品牌查询
            if (StringUtils.isNotBlank(vo.getBrand())) {
                Query brandQuery = TermQuery.of(brand -> brand.field("brand").value(vo.getBrand()))._toQuery();
                listQuery.add(brandQuery);
            }
            //规格查询
            if(!CollectionUtils.isEmpty(vo.getSpec())){

                //规格为数组-须要进行拆分
                for (String s : vo.getSpec()) {
                    String[] split = s.split(":");          //split[0]: 代表规格名称 split[1]: 代表规格选项的值
                    Query specQuery = TermQuery.of(spec -> spec.field("specMap." + split[0] + ".keyword").value(split[1]))._toQuery();
                    listQuery.add(specQuery);
                }
            }


            //定义价格区间查询
            if(StringUtils.isNotBlank(vo.getPrice())){
                //拆份价格
                String[] split = vo.getPrice().split(":");  // split[0]:字段名 split[1]: 价格范围区间，后面进行二次拆分
                //根据最大值设置查询
                Query priceRangeQuery = null;
                String[] priceRange = split[1].split("-");  // priceRange[0]: 最小值 priceRange[1]: 最大值
                if(!priceRange[1].equals("*")){    //gte就是大于等于的
                    priceRangeQuery = RangeQuery.of(r -> r.field(split[0]).gte(JsonData.of(priceRange[0])).lte(JsonData.of(priceRange[1])))._toQuery();
                }else{
                    //拼凑得出范围查询。
                    priceRangeQuery = RangeQuery.of(r -> r.field(split[0]).gte(JsonData.of(priceRange[0])))._toQuery();
                }
                //将上面的范围查询放到listQuery中
                listQuery.add(priceRangeQuery);
            }
            //商品排序   价格的升序和降序。   排序的关键字
            SortOptions priceSortOptions = getSortOptions(vo);
            //定义排序集合
            List<SortOptions> optionList = new ArrayList<>();
            //高亮查询字段map集合
            Map<String, HighlightField> hightlightFieldMap = new HashMap<>();
            //设置高亮查询的前缀信息
            List<String> hightlightPreTags = new ArrayList<>();
            //设置高亮查询后缀信息集合
            List<String> hightlightPostTags = new ArrayList<>();
            //取出分页参数    //
            int page = vo.getPage();
            int pageSize = vo.getPageSize();
            //定义查询请求对象
            SearchRequest  request = SearchRequest.of(r -> r
                    .index("item")
                    .query(q -> q.matchAll(ma -> ma))
                    .query(q -> q.bool(b -> b.filter(listQuery)))
                    .aggregations("category-agg",categoryAgg)
                    .sort(optionList)   //多字段或者单字段排序
                    .highlight(h -> h.fields(hightlightFieldMap).preTags(hightlightPreTags).postTags(hightlightPostTags))
                    //size 每页几条 -- form 那一页开始
                    .from((page - 1)*pageSize)
                    .size(pageSize)
            );
            //如果有价格排序就添加到排序集合中
            if(priceSortOptions != null){
                optionList.add(priceSortOptions);
            }
            //查询关键字是否有值
            if(StringUtils.isNotBlank(vo.getKeywords())) {
                //定义多字段匹配查询
                Query multiMatchQuery = MultiMatchQuery.of(mmq -> mmq.fields("title", "category", "brand").query(vo.getKeywords()))._toQuery();
                listQuery.add(multiMatchQuery);
                //添加高亮查询字段信息
                hightlightFieldMap.put("title",HighlightField.of(hf -> hf));
                //添加高亮查询前缀及后缀信息
                hightlightPreTags.add("<span style='color:red'>");
                hightlightPostTags.add("</span>");
            }
            /*--------对于查询结果-----来进行处理------------*/
            //得到查询结果
            SearchResponse<ItemEntity> searchResponse = client.search(request, ItemEntity.class);
            //将查询结果进行处理
            //得到商品分类
            Aggregate aggregate = searchResponse.aggregations().get("category-agg");
            StringTermsAggregate sterms = aggregate.sterms();
            //将分类数据放到分类集合中
            if(sterms != null){
                List<StringTermsBucket> termsBuckets = sterms.buckets().array();
                for (StringTermsBucket termsBucket : termsBuckets) {
                    categoryList.add(termsBucket.key());
                }
            }
            System.out.println("categoryList = " + categoryList);
            //根据前端传入的分类，查看是否有值，如果没有值就将上面的分类集合中的第一个值取出
            String category = "";           //分类名称
            if(StringUtils.isBlank(vo.getCategory())){
                if(!CollectionUtils.isEmpty(categoryList)){
                    category = categoryList.get(0);
                }
            }else{
                category = vo.getCategory();
            }
            //根据分类在redis中查询品牌列表及规格列表
            Map brandAndSpecList = findBrandAndSpecList(category);
            //将品牌列表及规格列表放回到resultMap中
            resultMap.putAll(brandAndSpecList);
            //放分类列表到resultMap中
            resultMap.put("categoryList",categoryList);
            //放商品列表到resultMap中
            //得到searchResponse中的hits的值
            HitsMetadata<ItemEntity> hits = searchResponse.hits();
            // 用于分页的总记录数  得到总记录数
            long total = hits.total().value();
            System.out.println("total = " + total);
            //计算总页数   转换double得到小数型   在转换为int型
            int totalPage = (int) Math.ceil(total/(double)pageSize);

            for (Hit<ItemEntity> hit : hits.hits()) {
                //得到原数据
                ItemEntity itemEntity = hit.source();
                //得到高亮查询数据
                Map<String, List<String>> highlight = hit.highlight();  // key：代表高亮查询的字段名,值：代表高亮查询的结果数据
                List<String> list = highlight.get("title");
                //将高亮查询数据覆盖原来的值
                if(!CollectionUtils.isEmpty(list)){
                    itemEntity.setTitle(list.get(0));
                }
                //将高亮查询数据添加到商品列表中
                itemList.add(itemEntity);
            }
            //将itemList放到大集合中
            resultMap.put("itemList",itemList);

            //将总记录数及总页数放到resultMap集合中 --用于分页查询
            resultMap.put("total",total);
            resultMap.put("totalPage",totalPage);
            System.out.println(totalPage);
        }
        return resultMap;
    }

    //得到前端传入的排序相关信息，构造并返回SortOptions对象
    private SortOptions getSortOptions(ItemVo vo) {
        SortOptions sortOptions = null;
        if(StringUtils.isNotBlank(vo.getSort())){
            String[] split = vo.getSort().split(":");     //以：进行拆分
            //根据排序关键字是asc还是desc，确定是升序还是降序排序
            if(split[0].equals("asc")) {
                sortOptions = SortOptions.of(s -> s.field(sortField -> sortField.field(split[1]).order(SortOrder.Asc)));
            }else{
                sortOptions = SortOptions.of(s -> s.field(sortField -> sortField.field(split[1]).order(SortOrder.Desc)));
            }
        }
        return sortOptions;
    }

    /**
     * 根据分类名称在redis中查询规格及品牌列表
     * @param category
     * @return
     */
    private Map findBrandAndSpecList(String category) {
        //根据分类名称查询出模板id
        BoundHashOperations<String, String, String> boundHashOperations = redisTemplate.boundHashOps("itemCatList");
        //构造返回map
        Map map = new HashMap();
        if(StringUtils.isNotBlank(category)){
            String typeId = boundHashOperations.get(category);
            //根据模板id得到品牌及规格列表
            String brandList = (String) redisTemplate.boundHashOps("brandList").get(typeId);
            List<Map> brands = JSON.parseArray(brandList,Map.class);
            String specList = (String) redisTemplate.boundHashOps("specList").get(typeId);
            List<Map> specs = JSON.parseArray(specList,Map.class);
            //将品牌及规格列表放到map中
            map.put("specList",specs);
            map.put("brandList",brands);
        }else{
            map.put("specList",new ArrayList<>());
            map.put("brandList",new ArrayList<>());
        }
        return map;

    }
}